package dinucleotides;

import java.util.ArrayList;
import java.util.Collections;
import java.util.EnumMap;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

public class GenerateurCodes {
	
	// codes répondant aux différends algos classés par longueur
	private Map<Algo, List<List<Code>>> codes;
	private TesteurCode test;
	private List<String> listPaires;
	private int nbPaires;
	
	public GenerateurCodes() {
		Map<Character,Character> compl = new HashMap<Character,Character>();
		compl.put('A', 'T');
		compl.put('T', 'A');
		compl.put('C', 'G');
		compl.put('G', 'C');
		
		List<String> alphabet = new ArrayList<String>();
		alphabet.add("A");
		alphabet.add("C");
		alphabet.add("G");
		alphabet.add("T");
		init(alphabet, compl);
	}
	
	public GenerateurCodes(List<String> alphabet) {
		Map<Character,Character> compl = new HashMap<Character,Character>();
		for (String s : alphabet) {
			compl.put(s.charAt(0), s.charAt(0));
		}
		init(alphabet,compl);
	}
	
	public GenerateurCodes(List<String> alphabet, Map<Character,Character> compl) {
		init(alphabet, compl);
	}
	
	private void init(List<String> alphabet, Map<Character,Character> compl) {
		listPaires = new ArrayList<String>();
		nbPaires = 0;
		
		for (int i = 0; i < alphabet.size(); i++) {
			for (int j = i+1; j < alphabet.size(); j++) {
				listPaires.add(alphabet.get(i)+ alphabet.get(j));
				listPaires.add(alphabet.get(j)+ alphabet.get(i));
				nbPaires++;
			}
		}
		
		//On crée un testeur avec le tableau de complémentaires
		test = new TesteurCode(compl);
		//On crée les listes de codes
		codes = new EnumMap<Algo, List<List<Code>>>(Algo.class);
	}
	
	public int getNbPaires() {
		return this.nbPaires;
	}
	
	private void genCodesCirculaires() {
		Set<Code> codesATester = new HashSet<Code>();
		List<List<Code>> codesCirculaires = new ArrayList<List<Code>>();
		int i = 1;

		this.codes.put(Algo.CIRCULAIRES, codesCirculaires);

		initCodesLongueur1(codesCirculaires);

		// Rajout des codes de longueur 2
		for (Code c : codesCirculaires.get(0)) {
			codesATester.addAll(c.getCodesSuivants());
		}

		while (codesATester.size() != 0) {
			codesCirculaires.add(new ArrayList<Code>());
			// Test des codes
			codesCirculaires.get(i).addAll(test.supprimeCodesNonCirculaires(codesATester));
			codesATester.clear();

			// Rajout des codes de longueur n + 1
			for (Code c : codesCirculaires.get(i)) {
				codesATester.addAll(c.getCodesSuivants());
			}
			i++;
		}
	}
	
	// Rajoute les codes de longueur 1 à la liste
	private void initCodesLongueur1(List<List<Code>> listCodes) {
		Code code;
		listCodes.add(new ArrayList<Code>());

		for (int i = 0; i < nbPaires; i++) {
			code = new Code(nbPaires, listPaires);
			code.rajouterPaire(i, 0);
			listCodes.get(0).add(code);
			code = new Code(nbPaires, listPaires);
			code.rajouterPaire(i, 1);
			listCodes.get(0).add(code);
		}
	}
	
	private void genCodesAutoComplementaires(List<List<Code>> list) {
		List<List<Code>> codesCirc = this.codes.get(Algo.CIRCULAIRES);

		for (int i = 0; i < codesCirc.size(); i++) {
			list.add(new ArrayList<Code>());
			list.get(i).addAll(test.supprimeCodesNonAutoCompl(codesCirc.get(i)));
		}
	}
	
	private void genCodesMaximaux(List<List<Code>> list) {
		List<List<Code>> codesCirc = this.codes.get(Algo.CIRCULAIRES);
		List<Code> listTemp;

		for (int i = 0; i < codesCirc.size() - 1; i++) {
			list.add(new ArrayList<Code>());
			for (Code c : codesCirc.get(i)) {
				// On prend les codes issu de c
				listTemp = c.getCodesSuivants();
				// On ne retient que les circulaires
				listTemp.retainAll(codesCirc.get(i + 1));

				if (listTemp.size() == 0) {
					// S'il n'y a pas de code circulaires dans les suivants
					// le code c est maximal
					list.get(i).add(c);
				}
			}
		}

		// On rajoute les codes de longueurs maximales
		list.add(new ArrayList<Code>());
		list.get(list.size() - 1).addAll(codesCirc.get(list.size() - 1));
	}
	
	private void genCodesComplMax(List<List<Code>> list) {
		for (int i = 0; i < this.codes.get(Algo.AUTOCOMPLEMENTAIRES).size(); i++) {
			list.add(new ArrayList<Code>());
			// On ajoute tous les autocomplémentaires
			list.get(i).addAll(this.codes.get(Algo.AUTOCOMPLEMENTAIRES).get(i));
			// On ne retient que ceux qui sont aussi maximaux
			list.get(i).retainAll(this.codes.get(Algo.MAXIMAUX).get(i));
		}
	}
	
	/** Génère les codes pour un algo
	 * @param algo
	 */
	public void genCodes(Algo algo) {
		if (!this.codes.containsKey(Algo.CIRCULAIRES)) {
			genCodesCirculaires();
		}

		if (!this.codes.containsKey(algo)) {
			// On a pas encore calculé cette algo
			// On le rajoute
			List<List<Code>> list = new ArrayList<List<Code>>();
			this.codes.put(algo, list);

			switch (algo) {
			case AUTOCOMPLEMENTAIRES:
				genCodesAutoComplementaires(list);
				break;
			case MAXIMAUX:
				genCodesMaximaux(list);
				break;
			case MAX_AUTOCOMPL:
				// On doit déjà avoir fait autocomplémentaires et maximaux
				genCodes(Algo.AUTOCOMPLEMENTAIRES);
				genCodes(Algo.MAXIMAUX);
				genCodesComplMax(list);
				break;
			default:
				break;
			}
		}
	}
	
	/** Retourne un tableau contenant le nombre d'élèments de chaque liste de la liste de liste
	 * @param list
	 * @return
	 */
	private static int[] getNbCodes(List<List<Code>> list) {
		int[] res = new int[list.size()];

		for (int i = 0; i < list.size(); i++) {
			res[i] = list.get(i).size();
		}

		return res;
	}
	
	/** Retourne un tableau qui contient le nombre de codes pour un algo classé selon leur longueur
	 * @param algo
	 * @return
	 */
	public int[] getNbCodes(Algo algo) {
		genCodes(algo);
		return getNbCodes(this.codes.get(algo));
	}
	
	/** Retourne les codes de la longueur 'longueur' pour un algo donné
	 * @param algo
	 * @param longueur
	 * @return
	 */
	public List<Code> getCodes(Algo algo, int longueur) {
		genCodes(algo);
		Collections.sort(this.codes.get(algo).get(longueur-1));
		return this.codes.get(algo).get(longueur-1);
	}
}